中文

通过我们完整的实现指南,掌握 JavaScript 设计模式。学习创建型、结构型和行为型模式,并附有实用的代码示例。

JavaScript 设计模式:现代开发者综合实现指南

引言:构建健壮代码的蓝图

在瞬息万变的软件开发世界里,编写能够正常工作的代码仅仅是第一步。真正的挑战,也是专业开发者的标志,是创建可扩展、可维护且易于他人理解和协作的代码。这正是设计模式发挥作用的地方。它们不是特定的算法或库,而是用于解决软件架构中反复出现问题的高级、与语言无关的蓝图。

对于 JavaScript 开发者而言,理解和应用设计模式比以往任何时候都更加重要。随着应用程序的复杂性不断增加,从复杂的前端框架到 Node.js 上强大的后端服务,一个坚实的架构基础是必不可少的。设计模式提供了这个基础,它们提供了经过实战检验的解决方案,以促进松散耦合、关注点分离和代码复用。

本综合指南将引导您了解设计模式的三个基本类别,提供清晰的解释和实用的现代 JavaScript (ES6+) 实现示例。我们的目标是让您掌握为特定问题识别使用哪种模式的知识,以及如何在您的项目中有效地实现它。

设计模式的三大支柱

设计模式通常分为三大类,每一类都针对一组独特的架构挑战:

让我们通过实际示例深入探讨每个类别。


创建型模式:掌握对象创建

创建型模式提供了各种对象创建机制,这增加了灵活性和现有代码的复用。它们有助于将系统与其对象的创建、组合和表示方式解耦。

单例模式 (The Singleton Pattern)

概念: 单例模式确保一个类只有一个实例,并提供一个全局唯一的访问点。任何创建新实例的尝试都将返回原始实例。

常见用例: 此模式对于管理共享资源或状态非常有用。例如,单个数据库连接池、全局配置管理器,或应在整个应用程序中统一的日志记录服务。

JavaScript 实现: 现代 JavaScript,特别是 ES6 的类,使得实现单例模式变得非常直接。我们可以在类上使用一个静态属性来保存这个唯一的实例。

示例:日志服务单例

class Logger { constructor() { if (Logger.instance) { return Logger.instance; } this.logs = []; Logger.instance = this; } log(message) { const timestamp = new Date().toISOString(); this.logs.push({ message, timestamp }); console.log(`${timestamp} - ${message}`); } getLogCount() { return this.logs.length; } } // 虽然调用了 'new' 关键字,但构造函数逻辑确保了只有一个实例。 const logger1 = new Logger(); const logger2 = new Logger(); console.log("两个 logger 是同一个实例吗?", logger1 === logger2); // true logger1.log("来自 logger1 的第一条消息。"); logger2.log("来自 logger2 的第二条消息。"); console.log("日志总数:", logger1.getLogCount()); // 2

优缺点:

工厂模式 (The Factory Pattern)

概念: 工厂模式提供一个用于在超类中创建对象的接口,但允许子类改变将要创建的对象的类型。它的核心是使用一个专门的“工厂”方法或类来创建对象,而无需指定它们的具体类。

常见用例: 当一个类无法预知它需要创建的对象类型时,或者当您想为库的用户提供一种创建对象的方式,而无需他们了解内部实现细节时。一个常见的例子是根据参数创建不同类型的用户(如管理员、会员、访客)。

JavaScript 实现:

示例:用户工厂

class RegularUser { constructor(name) { this.name = name; this.role = 'Regular'; } viewDashboard() { console.log(`${this.name} 正在查看用户仪表盘。`); } } class AdminUser { constructor(name) { this.name = name; this.role = 'Admin'; } viewDashboard() { console.log(`${this.name} 正在查看具有完全权限的管理员仪表盘。`); } } class UserFactory { static createUser(type, name) { switch (type.toLowerCase()) { case 'admin': return new AdminUser(name); case 'regular': return new RegularUser(name); default: throw new Error('指定了无效的用户类型。'); } } } const admin = UserFactory.createUser('admin', 'Alice'); const regularUser = UserFactory.createUser('regular', 'Bob'); admin.viewDashboard(); // Alice 正在查看具有完全权限的管理员仪表盘。 regularUser.viewDashboard(); // Bob 正在查看用户仪表盘。 console.log(admin.role); // Admin console.log(regularUser.role); // Regular

优缺点:

原型模式 (The Prototype Pattern)

概念: 原型模式是通过复制一个现有对象(称为“原型”)来创建新对象。您不是从头开始构建对象,而是创建一个预配置对象的克隆。这是 JavaScript 本身通过原型继承工作的基本方式。

常见用例: 当创建对象的成本比复制现有对象更昂贵或更复杂时,此模式很有用。它也用于创建在运行时指定其类型的对象。

JavaScript 实现: JavaScript 通过 `Object.create()` 对此模式提供了内置支持。

示例:可克隆的车辆原型

const vehiclePrototype = { init: function(model) { this.model = model; }, getModel: function() { return `这辆车的型号是 ${this.model}`; } }; // 基于车辆原型创建一个新的 car 对象 const car = Object.create(vehiclePrototype); car.init('福特野马'); console.log(car.getModel()); // 这辆车的型号是 福特野马 // 创建另一个对象,一辆卡车 const truck = Object.create(vehiclePrototype); truck.init('特斯拉 Cybertruck'); console.log(truck.getModel()); // 这辆车的型号是 特斯拉 Cybertruck

优缺点:


结构型模式:智能地组装代码

结构型模式关注如何将对象和类组合成更大、更复杂的结构。它们专注于简化结构和识别关系。

适配器模式 (The Adapter Pattern)

概念: 适配器模式充当两个不兼容接口之间的桥梁。它涉及一个单一的类(适配器),该类连接了独立或不兼容接口的功能。可以把它想象成一个电源适配器,让您可以将设备插入外国的电源插座。

常见用例: 将一个新的第三方库与期望不同 API 的现有应用程序集成,或者在不重写旧代码的情况下使旧代码与现代系统协同工作。

JavaScript 实现:

示例:将新 API 适配到旧接口

// 我们的应用程序使用的旧有接口 class OldCalculator { operation(term1, term2, operation) { switch (operation) { case 'add': return term1 + term2; case 'sub': return term1 - term2; default: return NaN; } } } // 具有不同接口的闪亮新库 class NewCalculator { add(term1, term2) { return term1 + term2; } subtract(term1, term2) { return term1 - term2; } } // 适配器类 class CalculatorAdapter { constructor() { this.calculator = new NewCalculator(); } operation(term1, term2, operation) { switch (operation) { case 'add': // 将调用适配到新接口 return this.calculator.add(term1, term2); case 'sub': return this.calculator.subtract(term1, term2); default: return NaN; } } } // 客户端代码现在可以像使用旧计算器一样使用适配器 const oldCalc = new OldCalculator(); console.log("旧计算器结果:", oldCalc.operation(10, 5, 'add')); // 15 const adaptedCalc = new CalculatorAdapter(); console.log("适配后计算器结果:", adaptedCalc.operation(10, 5, 'add')); // 15

优缺点:

装饰器模式 (The Decorator Pattern)

概念: 装饰器模式允许您在不改变对象原始代码的情况下,动态地为对象附加新的行为或职责。这是通过将原始对象包装在一个包含新功能的特殊“装饰器”对象中来实现的。

常见用例: 为 UI 组件添加功能,为用户对象增加权限,或为服务添加日志/缓存行为。它是子类化的一种灵活替代方案。

JavaScript 实现: 在 JavaScript 中,函数是一等公民,这使得实现装饰器变得容易。

示例:装饰一份咖啡订单

// 基础组件 class SimpleCoffee { getCost() { return 10; } getDescription() { return '简单咖啡'; } } // 装饰器 1: 牛奶 function MilkDecorator(coffee) { const originalCost = coffee.getCost(); const originalDescription = coffee.getDescription(); coffee.getCost = function() { return originalCost + 2; }; coffee.getDescription = function() { return `${originalDescription},加牛奶`; }; return coffee; } // 装饰器 2: 糖 function SugarDecorator(coffee) { const originalCost = coffee.getCost(); const originalDescription = coffee.getDescription(); coffee.getCost = function() { return originalCost + 1; }; coffee.getDescription = function() { return `${originalDescription},加糖`; }; return coffee; } // 让我们创建并装饰一杯咖啡 let myCoffee = new SimpleCoffee(); console.log(myCoffee.getCost(), myCoffee.getDescription()); // 10, 简单咖啡 myCoffee = MilkDecorator(myCoffee); console.log(myCoffee.getCost(), myCoffee.getDescription()); // 12, 简单咖啡,加牛奶 myCoffee = SugarDecorator(myCoffee); console.log(myCoffee.getCost(), myCoffee.getDescription()); // 13, 简单咖啡,加牛奶,加糖

优缺点:

外观模式 (The Facade Pattern)

概念: 外观模式为一个复杂的类、库或 API 子系统提供一个简化的、高层次的接口。它隐藏了底层的复杂性,使子系统更易于使用。

常见用例: 为一组复杂操作创建一个简单的 API,例如电子商务的结账流程,其中涉及库存、支付和运输等子系统。另一个例子是,用一个方法来启动一个 Web 应用程序,该方法内部配置了服务器、数据库和中间件。

JavaScript 实现:

示例:抵押贷款申请外观

// 复杂的子系统 class BankService { verify(name, amount) { console.log(`正在为 ${name} 核实金额为 ${amount} 的足够资金`); return amount < 100000; } } class CreditHistoryService { get(name) { console.log(`正在检查 ${name} 的信用记录`); // 模拟良好的信用评分 return true; } } class BackgroundCheckService { run(name) { console.log(`正在对 ${name} 进行背景调查`); return true; } } // 外观 class MortgageFacade { constructor() { this.bank = new BankService(); this.credit = new CreditHistoryService(); this.background = new BackgroundCheckService(); } applyFor(name, amount) { console.log(`--- 为 ${name} 申请抵押贷款 ---`); const isEligible = this.bank.verify(name, amount) && this.credit.get(name) && this.background.run(name); const result = isEligible ? '已批准' : '已拒绝'; console.log(`--- ${name} 的申请结果: ${result} ---\n`); return result; } } // 客户端代码与简单的外观接口交互 const mortgage = new MortgageFacade(); mortgage.applyFor('John Smith', 75000); // 已批准 mortgage.applyFor('Jane Doe', 150000); // 已拒绝

优缺点:


行为型模式:协调对象通信

行为型模式完全是关于对象如何相互通信,专注于分配职责和有效管理交互。

观察者模式 (The Observer Pattern)

概念: 观察者模式定义了对象之间的一对多依赖关系。当一个对象(“主题”或“可观察对象”)改变其状态时,其所有依赖对象(“观察者”)都会被自动通知和更新。

常见用例: 此模式是事件驱动编程的基础。它被广泛用于 UI 开发(DOM 事件监听器)、状态管理库(如 Redux 或 Vuex)和消息传递系统。

JavaScript 实现:

示例:新闻机构和订阅者

// 主题 (可观察对象) class NewsAgency { constructor() { this.subscribers = []; } subscribe(subscriber) { this.subscribers.push(subscriber); console.log(`${subscriber.name} 已订阅。`); } unsubscribe(subscriber) { this.subscribers = this.subscribers.filter(sub => sub !== subscriber); console.log(`${subscriber.name} 已取消订阅。`); } notify(news) { console.log(`--- 新闻机构: 广播新闻: "${news}" ---`); this.subscribers.forEach(subscriber => subscriber.update(news)); } } // 观察者 class Subscriber { constructor(name) { this.name = name; } update(news) { console.log(`${this.name} 收到了最新消息: "${news}"`); } } const agency = new NewsAgency(); const sub1 = new Subscriber('读者 A'); const sub2 = new Subscriber('读者 B'); const sub3 = new Subscriber('读者 C'); agency.subscribe(sub1); agency.subscribe(sub2); agency.notify('全球市场上涨!'); agency.subscribe(sub3); agency.unsubscribe(sub2); agency.notify('宣布了新的技术突破!');

优缺点:

策略模式 (The Strategy Pattern)

概念: 策略模式定义了一系列可互换的算法,并将每个算法封装在自己的类中。这使得算法可以在运行时被选择和切换,而与使用它的客户端无关。

常见用例: 为电子商务网站实现不同的排序算法、验证规则或运输成本计算方法(例如,固定费率、按重量、按目的地)。

JavaScript 实现:

示例:运输成本计算策略

// 上下文 (Context) class Shipping { constructor() { this.company = null; } setStrategy(company) { this.company = company; console.log(`运输策略设置为: ${company.constructor.name}`); } calculate(pkg) { if (!this.company) { throw new Error('尚未设置运输策略。'); } return this.company.calculate(pkg); } } // 策略 (Strategies) class FedExStrategy { calculate(pkg) { // 基于重量等的复杂计算 const cost = pkg.weight * 2.5 + 5; console.log(`FedEx 对 ${pkg.weight}kg 包裹的费用为 $${cost}`); return cost; } } class UPSStrategy { calculate(pkg) { const cost = pkg.weight * 2.1 + 4; console.log(`UPS 对 ${pkg.weight}kg 包裹的费用为 $${cost}`); return cost; } } class PostalServiceStrategy { calculate(pkg) { const cost = pkg.weight * 1.8; console.log(`邮政服务对 ${pkg.weight}kg 包裹的费用为 $${cost}`); return cost; } } const shipping = new Shipping(); const packageA = { from: '纽约', to: '伦敦', weight: 5 }; shipping.setStrategy(new FedExStrategy()); shipping.calculate(packageA); shipping.setStrategy(new UPSStrategy()); shipping.calculate(packageA); shipping.setStrategy(new PostalServiceStrategy()); shipping.calculate(packageA);

优缺点:


现代模式与架构考量

虽然经典设计模式是永恒的,但 JavaScript 生态系统已经发展,催生了现代的诠释和大规模的架构模式,这些对于今天的开发者至关重要。

模块模式 (The Module Pattern)

模块模式是 ES6 之前 JavaScript 中最流行的用于创建私有和公共作用域的模式之一。它使用闭包来封装状态和行为。如今,该模式已在很大程度上被原生的 ES6 模块 (`import`/`export`) 所取代,后者提供了一个标准化的、基于文件的模块系统。理解 ES6 模块对任何现代 JavaScript 开发者来说都是基础,因为它们是组织前端和后端应用程序代码的标准。

架构模式 (MVC, MVVM)

区分设计模式架构模式很重要。设计模式解决特定的、局部的问题,而架构模式为整个应用程序提供高层次的结构。

当使用像 React、Vue 或 Angular 这样的框架时,您本质上就在使用这些架构模式,通常还结合了更小的设计模式(如用于状态管理的观察者模式)来构建健壮的应用程序。


结论:明智地使用模式

JavaScript 设计模式不是僵化的规则,而是开发者工具箱中的强大工具。它们代表了软件工程界的集体智慧,为常见问题提供了优雅的解决方案。

掌握它们的关键不是记住每一种模式,而是理解每种模式所解决的问题。当您在代码中面临挑战时——无论是紧密耦合、复杂的对象创建,还是不灵活的算法——您都可以将相应的模式作为明确定义的解决方案。

我们最后的建议是: 从编写能工作的最简单的代码开始。随着您的应用程序演进,在模式自然适用的地方将您的代码重构为这些模式。不要在不需要的地方强行使用模式。通过明智地应用它们,您编写的代码将不仅功能齐全,而且整洁、可扩展,并且在未来几年内都易于维护。